package sz.com.postoffice.jxyzwgcase.excellistener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import lombok.extern.slf4j.Slf4j;
import sz.com.postoffice.jxyzwgcase.dao.master.PersonnelSectionDao;
import sz.com.postoffice.jxyzwgcase.pojo.PersonnelSectionInformation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName PersonnelSectionInformationListener
 * @Deacription 人员及段道信息（营业部填报） excel导入监听类
 * @Author 王盟
 * @Date 2020/9/28 13:59
 * @Version 1.0
 **/
@Slf4j
public class PersonnelSectionListener extends AnalysisEventListener<PersonnelSectionInformation> {
    /**
     * 每隔5条存储数据库，实际使用中可以3000条，然后清理list ，方便内存回收
     */
    private static final int BATCH_COUNT = 3000;
    List<PersonnelSectionInformation> list = new ArrayList<PersonnelSectionInformation>();

    private Map<Integer, String> headMap = new HashMap<>(); // 导入模板头内容

    private Boolean insertFlag = true;
    /**
     * 假设这个是一个DAO，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private PersonnelSectionDao personnelSectionDao;

    //校验失败的单元格
    private List<String> failList = new ArrayList<>();

    // 导入成功条数
    private int successCount=0;
    public int getSuccessCount() {
        return successCount;
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param personnelSectionDao
     */
    public PersonnelSectionListener(PersonnelSectionDao personnelSectionDao) {
        this.personnelSectionDao = personnelSectionDao;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(PersonnelSectionInformation data, AnalysisContext context) {
/*
        int rowIndex=context.readRowHolder().getRowIndex()+1;// 当前行

        log.info("解析到一条数据:{}", JSON.toJSONString(data));

        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
        // 此处加上校验
        Validator validator = HibernateValidators.validator();
        Set<ConstraintViolation<PersonnelSectionInformation>> validateSet = validator.validate(data, Default.class);

        if (validateSet != null && !validateSet.isEmpty()) {
            //System.out.println("校验出错：" + data);

            StringBuilder failStr=new StringBuilder("营业部名称为：%s 人员姓名：%s 岗位：%s excel表格中第%d行存在: ");
            validateSet.forEach(x->failStr.append(x.getMessage()+" "));
            failList.add(String.format(failStr.toString(),data.getSalesname(),data.getUsername(),data.getPost(),rowIndex));

            log.info("校验出错: {}",data);
            // failList.add();
            insertFlag = false;
        }else {
            successCount++;
            list.add(data);
        }
         if("揽投员".equals(data.getPost()) && ("".equals(data.getSystemname()) || data.getSystemname() ==null)){
            insertFlag = false;
            failList.add(String.format("营业部名称为：%s 人员姓名：%s 岗位：%s excel表格中第%d行存在:%s",data.getSalesname(),data.getUsername(),data.getPost(),rowIndex,"职位信息为揽投员时，段道名称不能为空"));
        }*/
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
        if (insertFlag){
            saveData();
        }else {
            log.info("有校验错误不入库");
        }
        log.info("所有数据解析完成！");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据，开始存储数据库！", list.size());
        personnelSectionDao.insertList(list);
        log.info("存储数据库成功！");
    }

    /*
    **
     * @description: 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     * @param:  * @param exception
     * @param context
     * @author wangmeng
     * @date: 2020/9/30 12:31
     */
    @Override
    public void onException(Exception exception, AnalysisContext context){
        log.error("解析失败，但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        int col = 0,row =0;
        String title = "";
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            col = excelDataConvertException.getColumnIndex();
            row = excelDataConvertException.getRowIndex();
            excelDataConvertException.getCellData();
            log.error("第{}行，第{}列解析异常，数据为:{}", row, col , excelDataConvertException.getCellData());
            title = this.headMap.get(col);
            log.info("出错标题列名称 -- "+ title);
            failList.add(String.format("第{}行，第{}列解析异常，数据为:{}", row, col , excelDataConvertException.getCellData()));
        }

    }

    @Override
    public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext var2){
        //是否手动传入表头信息 没有则自动读取实体类头信息

        Map<Integer, Head> map = var2.currentReadHolder().excelReadHeadProperty().getHeadMap();
        for (int i = 0; i < map.size(); i++) {
            //封装成headMap
            this.headMap.put(map.get(i).getColumnIndex(),map.get(i).getHeadNameList().get(0));//加入模板头
        }

    }
    public List<String> getFailList() {
        return failList;
    }
    public Boolean getInsertFlag() {
        return insertFlag;
    }
}